package models

import (
	"fmt"
	"gitchina/kly_service/data"
	"gitchina/kly_service/errors"
	"gitchina/kly_service/template"
	"gitchina/kly_service/util"
	"gitchina/kly_service/util/sms"
	"regexp"
	"strconv"

	"bytes"

	"time"

	_ "github.com/dchest/captcha"
)

const (
	default_inviter = "011010000000zr7"
	phone_pattern   = "^1[3|4|5|7|8][0-9]\\d{8}$"
	passwordPattern = "^^(?![a-zA-z]+$)(?!\\d+$)(?![!@#$%^&*_-]+$)(?![a-zA-z\\d]+$)(?![a-zA-z!@#$%^&*_-]+$)(?![\\d!@#$%^&*_-]+$)[a-zA-Z\\d!@#$%^&*_-]+$"
)

type Member struct{}

type AddMemberRQ struct {
	Phone         string `json:"phone"`
	VerifyCode    string `json:"verify_code"`
	Passwd        string `json:"password"`
	RegisterId    string `json:"register_id"`
	Platform      string `json:"platform"`
	Inviter       string `json:"inviter"`
	MemberType    int    `json:"-"`
	ImageCdn      string `json:"-"`
	StoreLocation string `json:"-"`
	BucketName    string `json:"-"`
}

type FindPasswdRQ struct {
	Phone  string `json:"phone"`
	Code   string `json:"verify_code"`
	Passwd string `json:"password"`
}

type UpdPasswdRQ struct {
	OldPasswd string `json:"old_password"`
	NewPasswd string `json:"new_password"`
}

type UpdNickNameRQ struct {
	NickName string `json:"nick_name"`
}

type SendRegistryVerifyCodeRQ struct {
	Phone       string `json:"phone"`
	CaptchaCode string `json:"captchaCode"`
	CaptchaId   string `json:"captchaId"`
}

type GetMemberInfoRQ struct {
	MemberId ID `json:"member_id"`
}

type MemberRS struct {
	Id           ID     `json:"member_id"`
	RawId        int    `json:"-"`
	Token        string `json:"token"`
	Name         string `json:"name"`
	NickName     string `json:"nick_name"`
	Phone        string `json:"phone"`
	MemberType   int    `json:"member_type"`
	Level        int    `json:"level"`
	AuthLevel    int    `json:"auth_level"`
	PhotoUrl     string `json:"photo_url"`
	QRCodeUrl    string `json:"qrcode_url"`
	InviterID    string `json:"inviter"`
	InviteCode   string `json:"invite_code"`
	IDCardFS     string `json:"-"`
	IDCardRS     string `json:"-"`
	IDCardNo     string `json:"id_card_no"`
	QRCodeExists bool   `json:"-"`
	State        int    `json:"-"`
	Score        string `json:"score"`
	Point        int    `json:"point"`
}

type MemberBriefInfoRS struct {
	Id       ID     `json:"member_id"`
	Name     string `json:"name"`
	NickName string `json:"nick_name"`
	PhotoUrl string `json:"photo_url"`
}

func (m Member) AddMember(rq *AddMemberRQ) (mbrId ID, err error) {
	var exists bool
	var inviterId int
	var repo data.MemberRepo

	if err = checkPassword(rq.Passwd); err != nil {
		return
	}

	if exists, err = repo.Exists(rq.Phone); !exists && err == nil {
		if err = addMemberCheck(rq); err == nil {
			if inviterId, err = getInviterID(rq.Inviter); err == nil {
				if id, err := repo.AddMember(&data.MemberDto{
					Phone:    rq.Phone,
					Passwd:   rq.Passwd,
					PhotoUrl: fmt.Sprintf("%sphoto.jpg", rq.ImageCdn),
					Inviter:  inviterId,
					TypeId:   rq.MemberType,
				}); err != nil {
					util.GetLogger().Error("[model] - add member error: %s", err.Error())
					err = errors.New(errors.MSG_Mbr_Add_Error)
				} else {
					AddPointForRegistion(int(id))
					mbrId.Encode(ID_Shard, ID_Ver, ID_Member_Category, id)
					m.AddQRCode(rq.ImageCdn, string(mbrId), rq.StoreLocation, rq.BucketName, int(id))
				}
			} else {
				err = errors.New(errors.MSG_Mbr_Invalid_Inviter)
			}
		}
	} else {
		if err != nil {
			util.GetLogger().Error("[model] - add member error: %s", err.Error())
			err = errors.New(errors.MSG_Mbr_Add_Error)
		} else {
			err = errors.New(errors.MSG_Mbr_Exists)
		}
	}

	return
}

// AddQRCode ...
func (m Member) AddQRCode(cdn, mbrId, location, bucketName string, id int) (url string) {
	var g General
	var repo data.MemberRepo

	buf := new(bytes.Buffer)
	inv := fmt.Sprintf(g.GetParams("invitation_url"), mbrId)
	if err := util.GenQRCode(buf, inv); err == nil {
		if store, err := NewStore(location); err == nil {
			fn := fmt.Sprintf("qrcode/%s.png", mbrId)
			if err := store.Options("fn", fn).Options("bucketname", bucketName).Save(buf); err == nil {
				if _, err := repo.AddQRCode(id, fmt.Sprintf("%sqrcode/%s.png", cdn, mbrId)); err != nil {
					util.GetLogger().Error("[model] - add qrcode error: %s", err.Error())
				} else {
					url = fmt.Sprintf("%sqrcode/%s.png", cdn, mbrId)
				}
			}
		}
	}
	return url
}

func (m Member) GetMemberIDByPhone(phone string) (id int, err error) {
	var repo data.MemberRepo
	return repo.GetMemberIdByPhone(phone)
}

func (m Member) GetMemberIDByVehicleNO(no string) (id int, err error) {
	var repo data.MemberRepo
	return repo.GetMemberIDByVehicleNO(no)
}

func (m Member) GetMemberInfo(mbrId ID) (rs MemberRS, err error) {
	var id int64
	var repo data.MemberRepo
	_, _, _, id, err = mbrId.Decode()
	if err != nil {
		util.GetLogger().Error("[model] - get member. unexpected mbrId:%s", string(mbrId))
		err = errors.New(errors.MSG_Mbr_Invalid)
	} else {
		if mbr, err := repo.GetMemberInfo(int(id)); err == nil {
			var vid ID
			vid.Encode(ID_Shard, ID_Ver, ID_Member_Category, int64(mbr.Inviter))
			rs = MemberRS{
				Id:         mbrId,
				Name:       mbr.Name,
				NickName:   mbr.NickName,
				Phone:      mbr.Phone,
				MemberType: mbr.TypeId,
				Level:      mbr.Level,
				AuthLevel:  mbr.AuthLevel,
				PhotoUrl:   mbr.PhotoUrl,
				QRCodeUrl:  mbr.QRCode,
				IDCardFS:   mbr.IDCardFS,
				IDCardRS:   mbr.IDCardRS,
				IDCardNo:   mbr.IDCardNo,
				InviterID:  string(vid),
				InviteCode: mbr.Phone,
				Point:      mbr.Point,
			}
			if mbr.ScoreCount == 0 {
				rs.Score = "0"
			} else {
				rs.Score = fmt.Sprintf("%.0f", float32(mbr.ScoreTotal)/float32(mbr.ScoreCount))
			}
		} else {
			util.GetLogger().Error("[model] - get member info error: %s", err.Error())
			err = errors.New(errors.MSG_Mbr_Get_Error)
		}
	}
	return
}

func (m Member) FindPasswd(phone, code, passwd string) (err error) {
	var ok bool
	var id int
	var repo data.MemberRepo
	if ok, err = repo.VerifyCode(phone, code); err == nil {
		if ok {
			if id, err = repo.GetMemberIdByPhone(phone); err == nil {
				var mbrId ID
				mbrId.Encode(ID_Shard, ID_Ver, ID_Member_Category, int64(id))
				if ok, err = repo.UpdPasswdByPhone(phone, passwd); !ok || err != nil {
					if err != nil {
						util.GetLogger().Error("[model] - find password error: %s", err.Error())
						err = errors.New(errors.MSG_Mbr_Find_Passwd_Error)
					} else {
						err = errors.New(errors.MSG_Mbr_Find_Passwd_Error_By_Same_Value)
					}
				}
				if ok {
					util.NewJMessageClient().ChangePwd(string(mbrId), passwd)
				}
			} else {
				util.GetLogger().Error("[model] - find password error: %s", err.Error())
				err = errors.New(errors.MSG_Mbr_Find_Passwd_Error)
			}
		} else {
			util.GetLogger().Error("[model] - code:%s not exists or expired.", code)
			err = errors.New(errors.MSG_Mbr_Invalid_VerifyCode)
		}
	} else {
		util.GetLogger().Error("[model] - find password error: %s", err.Error())
		err = errors.New(errors.MSG_Mbr_Invalid_VerifyCode)
	}

	// if ok, err = repo.VerifyCode(phone, code); ok && err == nil {
	// 	if id, err = repo.GetMemberIdByPhone(phone); err == nil {
	// 		var mbrId ID
	// 		mbrId.Encode(ID_Shard, ID_Ver, ID_Member_Category, int64(id))
	// 		if ok, err = repo.UpdPasswdByPhone(phone, passwd); !ok || err != nil {
	// 			if err != nil {
	// 				util.GetLogger().Error("[model] - find password error: %s", err.Error())
	// 			}
	// 			err = errors.New(errors.MSG_Mbr_Find_Passwd_Error)
	// 		}
	// 		if ok {
	// 			util.NewJMessageClient().ChangePwd(string(mbrId), passwd)
	// 		}
	// 	} else {
	// 		util.GetLogger().Error("[model] - find password error: %s", err.Error())
	// 		err = errors.New(errors.MSG_Mbr_Find_Passwd_Error)
	// 	}
	// } else {
	// 	util.GetLogger().Error("[model] - find password error: %s", err.Error())
	// 	err = errors.New(errors.MSG_Mbr_Invalid_VerifyCode)
	// }
	return
}

func (m Member) UpdPasswd(mbrId ID, oldPasswd, newPasswd string) (err error) {
	var ok bool
	var id int64
	var repo data.MemberRepo
	if _, _, _, id, err = mbrId.Decode(); err != nil {
		util.GetLogger().Error("[model] - get member. unexpected mbrId:%s", string(mbrId))
		err = errors.New(errors.MSG_Mbr_Invalid)
	} else {
		if ok, err = repo.UpdPasswordByPasswd(id, oldPasswd, newPasswd); !ok || err != nil {
			if err != nil {
				util.GetLogger().Error("[model] - update password error: %s", err.Error())
			}
			err = errors.New(errors.MSG_Mbr_Upd_Passwd_Error)
		}
		if ok {
			util.NewJMessageClient().ChangePwd(string(mbrId), newPasswd)
		}
	}
	return
}

func (m Member) SendRegistryVerifyCode(phone, clientIp, captchaId, captchaCode string) (err error) {
	var ok bool
	var g General
	var phoneCount, ipCount int
	var repo data.MemberRepo

	//if captcha.VerifyString(captchaId, captchaCode) {
	if err = checkPhone(phone); err == nil {
		var code = util.GenVerifyCode(6)
		if phoneCount, ipCount, err = repo.GetVerifyCodeSendPhoneAndIpCount(phone, clientIp); err == nil {
			phoneLimit, _ := strconv.Atoi(g.GetParams("send_verify_code_phone_limit_per_day"))
			ipLimit, _ := strconv.Atoi(g.GetParams("send_verify_code_ip_limit_per_day"))
			if phoneCount < phoneLimit && ipCount < ipLimit {
				if ok, err = repo.AddCode(phone, code, clientIp); ok {
					vendor := sms.NewYunpianApi(phone, template.SendRegistryVerifyCodeTplId)
					vendor.Add("code", code)
					sms.SendTo(vendor)
				} else {
					if err != nil {
						util.GetLogger().Error("[model] - send sms error: %s", err.Error())
					}
					err = errors.New(errors.MSG_Mbr_Send_SMS_Failed)
				}
			} else {
				err = errors.New(errors.MSG_Mbr_Too_Many_Try_VerifyCode)
			}
		} else {
			err = errors.New(errors.MSG_Mbr_Send_SMS_Failed)
		}
	}

	return
}

func (m Member) UpdateNickName(mbrID ID, nickName string) (err error) {
	var id int64
	if _, _, _, id, err = mbrID.Decode(); err != nil {
		util.GetLogger().Error("[model] - get member. unexpected mbrId:%s", string(mbrID))
		err = errors.New(errors.MSG_Mbr_Invalid)
	} else {
		var repo data.MemberRepo
		if err = repo.UpdateNickName(int(id), nickName); err == nil {
			util.NewJMessageClient().UpdateNickName(string(mbrID), nickName)
		}
	}
	return
}

func (m Member) CloseMember(mbrID ID, phone, token string) (err error) {
	var auth Auth
	var mid int64
	var repo data.MemberRepo
	if _, _, _, mid, err = mbrID.Decode(); err == nil {
		auth.Logout(token)
		if err = repo.UpdatePhoneAndState(int(mid), fmt.Sprintf("%s%d", phone, time.Now().Unix()), 2); err != nil {
			util.GetLogger().Error("[model - CloseMember]. error:%s", err.Error())
		}
	}
	if err != nil {
		err = errors.New(errors.MSG_Mbr_Close_Member_Fail)
	}
	return
}

func addMemberCheck(rq *AddMemberRQ) (err error) {
	if err = checkPhone(rq.Phone); err != nil {
		return
	}

	if len(rq.Passwd) < 6 {
		err = errors.New(errors.MSG_Mbr_Passwd_Too_Short)
		return
	}
	if !verifyCode(rq.Phone, rq.VerifyCode) {
		err = errors.New(errors.MSG_Mbr_Invalid_VerifyCode)
		return
	}
	return
}

func checkPhone(phone string) (err error) {
	var match bool
	if match, err = regexp.MatchString(phone_pattern, phone); !match {
		util.GetLogger().Error("[model] - unexpected phone: %s", phone)
		err = errors.New(errors.MSG_Mbr_Invalid_Phone)
	}
	return
}

func checkMemberType(mbrType int) error {
	var err error
	//1. carrier owner
	//2. cargo owner
	//3. corp
	//4. ship owner
	if mbrType < 1 || mbrType > 4 {
		err = errors.New(errors.MSG_Mbr_Invalid_MemberType)
	}
	return err
}

func verifyCode(phone, code string) bool {
	var r data.MemberRepo
	valid, _ := r.VerifyCode(phone, code)
	return valid
}

func getInviterID(val string) (id int, err error) {
	var inviterId int64
	var repo data.MemberRepo
	if val != "" {
		if _, _, _, inviterId, err = ID(val).Decode(); err == nil {
			id = int(inviterId)
		} else {
			if id, err = repo.GetMemberIdByPhone(val); id == 0 {
				if err == nil {
					err = fmt.Errorf("phone:%s not found", val)
				}
			}
		}
	}
	return
}

func checkPassword(passwd string) error {
	indNum := [4]int{0, 0, 0, 0}
	//spCode := []byte{'!', '@', '#', '$', '%', '^', '&', '*', '_', '-'}

	if len(passwd) < 8 {
		return errors.New(errors.MSG_Mbr_Password_Too_Simple)
	}

	passwdByte := []byte(passwd)

	for _, i := range passwdByte {
		if i >= 'A' && i <= 'Z' {
			indNum[0] = 1
			continue
		}

		if i >= 'a' && i <= 'z' {
			indNum[0] = 1
			continue
		}

		if i >= '0' && i <= '9' {
			indNum[1] = 1
			continue
		}

		//notEnd := 0
		//for _, s := range spCode {
		//	if i == s {
		//		indNum[3] = 1
		//		notEnd = 1
		//		break
		//	}
		//}
		//
		//if notEnd != 1 {
		//	return errors.New("Unsupport code")
		//}
	}

	codeCount := 0
	for _, i := range indNum {
		codeCount += i
	}

	if codeCount < 2 {
		return errors.New(errors.MSG_Mbr_Password_Too_Simple)
	}

	return nil
}
